/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.core.jdbc.persistence.query;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.jrose.core.jdbc.entity.EntityDefinition;
import org.jrose.core.jdbc.entity.field.PersistenceField;
import org.jrose.core.jdbc.entity.field.internal.ColumnField;
import org.jrose.core.util.ConversionUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class Insert<E extends Serializable> implements Serializable {
    private static final long serialVersionUID = -204705225898596353L;
    private static final ConversionService conversionService = ConversionUtils.getConversionService();

    @NonNull @Getter private final E entity;

    public static <T extends Serializable> Insert<T> where(T entity) {
        Assert.notNull(entity);
        return new Insert<T>(entity);
    }

    public Sql generateInsertSQL(EntityDefinition<E> entityDefinition) {
        Assert.notNull(entityDefinition);

        // prepare entity with default value
        for (PersistenceField field : entityDefinition.getAllFields()) {
            field.prepareDefaultValue(entity);
        }

        // prepare persist candidates
        PersistenceField autoIncFieldWithoutValue = null;
        List<PersistenceField> persistCandidates = new LinkedList<>();
        for (PersistenceField field : entityDefinition.getAllFields()) {
            if (field.isAutoIncField()) {
                Object value = field.get(entity);
                if (value == null) {
                    autoIncFieldWithoutValue = field;
                    continue;
                }
            } else {
                if (field.isPrimaryKeyField()) {
                    Assert.notNull(field.get(entity), "Primary key field '" + field.getFieldName() + "' must not be null");
                }
                if (field.getClass() == ColumnField.class && ((ColumnField) field).getAnnotation().notNull()) {
                    Assert.notNull(field.get(entity), "Not null field '" + field.getFieldName() + "' must not be null");
                }
                if (field.get(entity) == null) {
                    continue;   // filter all NULL field
                }
            }
            persistCandidates.add(field);
        }

        // prepare candidateSQL and namedParameters

        List<String> columns = new LinkedList<>();
        List<String> values = new LinkedList<>();
        Map<String, Object> namedParameters = new LinkedHashMap<>();

        final Map<String, AtomicInteger> parameterCounter = new HashMap<>();

        for (PersistenceField persistenceField : persistCandidates) {
            String field = persistenceField.getFieldName();
            Object value = persistenceField.get(entity);

            String column = persistenceField.getColumnName();
            String parameter = generateParameterName(parameterCounter, column);

            columns.add(column);
            values.add(":" + parameter);

            if (persistenceField.isEnumField() && value != null) {
                value = conversionService.convert(value, String.class);
            }
            namedParameters.put(parameter, value);
        }
        String columnSQL = "(" + StringUtils.join(columns, ",") + ")";
        String valueSQL = "VALUES (" + StringUtils.join(values, ",") + ")";

        Sql sql = new Sql(
                Arrays.asList(columnSQL, valueSQL),
                namedParameters);
        sql.setAutoIncField(autoIncFieldWithoutValue);
        return sql;
    }

    private String generateParameterName(Map<String, AtomicInteger> parameterCounter, String column) {
        if (parameterCounter.containsKey(column)) {
            int count = parameterCounter.get(column).getAndIncrement();
            return column + count;
        } else {
            parameterCounter.put(column, new AtomicInteger(0));
            return column;
        }
    }
}
